package com.mfz.common.encryption.rsa

import com.mfz.common.encryption.Encryption
import com.mfz.common.encryption.EncryptionMode
import java.io.ByteArrayOutputStream
import java.security.*
import java.util.*
import javax.crypto.Cipher

/**
 *
 * 在代码内可以使用此类进行生成密钥。
 * 也可以使用openssl 命令：
 *
 * 生成私钥：
 * openssl genrsa -out rsa_private_key.pem 2048
 * 根据私钥生成公钥
 * openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
 * 根据私钥转成pkcs8格式。（java使用）
 * openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
 * create by 2019-06-19.
 * @author yimin
 */
data class RsaKey(
	val publicKey: PublicKey,
	val privateKey: PrivateKey,
	/**
	 * 使用Base64.getEncoder().encodeToString()方法进行处理过后的String。
	 */
	val publicKeyString: String,
	/**
	 * 使用Base64.getEncoder().encodeToString()方法进行处理过后的String。
	 */
	val privateKeyString: String)

/**
 * 生成密钥对象的方法
 *
 * length ： 密码的长度
 */
fun generatorRSAKey(length: Int): RsaKey {
	val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
	keyPairGenerator.initialize(length, SecureRandom())

	val keyPair = keyPairGenerator.generateKeyPair()


	return RsaKey(
		keyPair.public,
		keyPair.private,
		Base64.getEncoder().encodeToString(keyPair.public.encoded),
		Base64.getEncoder().encodeToString(keyPair.private.encoded)
	)
}

internal val CHARSET = Charsets.UTF_8
internal const val RSA_ALGORITHM = "RSA"
//internal const val RSA_ALGORITHM_SIGN = "SHA256WithRSA"

abstract class AbstractRsa : Encryption {
	protected abstract val key: Key
	protected abstract val keyLength: Int

	override fun getMode(): String {
		return EncryptionMode.RSA.name
	}

	override fun decrypt(cipherText: String): String {
		try {
			val cipher = Cipher.getInstance(RSA_ALGORITHM)
			cipher.init(Cipher.DECRYPT_MODE, key)

			return String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(cipherText), keyLength), CHARSET)
		} catch (e: Exception) {
			throw RuntimeException("解密字符串[$cipherText]时遇到异常", e)
		}
	}

	override fun encrypt(planText: String): String {
		try {
			val cipher = Cipher.getInstance(RSA_ALGORITHM)
			cipher.init(Cipher.ENCRYPT_MODE, key)
			return Base64.getEncoder().encodeToString(
				rsaSplitCodec(
					cipher,
					Cipher.ENCRYPT_MODE,
					planText.toByteArray(CHARSET),
					keyLength
				)
			)
		} catch (e: Exception) {
			throw RuntimeException("加密字符串[$planText]时遇到异常", e)
		}
	}

	private fun rsaSplitCodec(cipher: Cipher, opmode: Int, datas: ByteArray, keySize: Int): ByteArray {
		val maxBlock: Int = if (opmode == Cipher.DECRYPT_MODE) {
			keySize / 8
		} else {
			keySize / 8 - 11
		}

		var offSet = 0
		var buff: ByteArray
		var i = 0
		try {
			ByteArrayOutputStream().use { out ->
				while (datas.size > offSet) {
					buff = if (datas.size - offSet > maxBlock) {
						cipher.doFinal(datas, offSet, maxBlock)
					} else {
						cipher.doFinal(datas, offSet, datas.size - offSet)
					}
					out.write(buff, 0, buff.size)
					i++
					offSet = i * maxBlock
				}

				return out.toByteArray()
			}
		} catch (e: Exception) {
			throw RuntimeException("加解密阀值为[$maxBlock]的数据时发生异常", e)
		}
	}

}